#region Using directives
using System;
using SharpWind.Helpers;
using Microsoft.DirectX;
using SharpWind.Math;
#endregion

namespace SharpWind.Scene
{
	public class FirstPersonCamera : Camera
	{
		#region Fields

		private Vector3 velocity; 

		private bool invertY;
		
		private bool enableYMovement;

		private float yaw;
		
		private float pitch;
		
		private float maxPitch;
		
		private float maxVelocity;

		#endregion

		#region Constructor

		public FirstPersonCamera()
		{
			this.maxPitch		 = MathHelper.ToRadians( 89.0f );
			this.maxVelocity	 = 1.0f;
			this.invertY		 = false;
			this.enableYMovement = true;

			CreateProjectionMatrix( MathHelper.Pi / 3.0f, 
									(float)Engine.Instance.Settings.Width / (float)Engine.Instance.Settings.Height , 
									1.0f, 1000.0f );
			Update();
		}

		#endregion

		#region Methods

		public void Move( Direction direction, float units )
		{
			switch( direction )
			{
				case Direction.Forward:
				{
					if( this.enableYMovement )
					{
						this.velocity += this.look * units;
					}
					else
					{
						Vector3 move = new Vector3( this.look.X, 0.0f, this.look.Z );
						move.Normalize();
						move *= units;
						this.velocity += move;
					}

					break;
				}

				case Direction.Up:
				{
					this.velocity.Y += units;
					break;
				}
			}
		}

		public void Strafe( float units )
		{
			this.velocity += this.right * units;
		}

		public void Yaw( float radians )
		{
			if( radians == 0.0f )
			{
				return;
			}

			Matrix rotation = Matrix.RotationAxis( this.up, radians );
			this.right		= Vector3.TransformNormal( this.right, rotation );
			this.look		= Vector3.TransformNormal( this.look, rotation );
		}

		public void Pitch( float radians )
		{
			if( radians == 0.0f )
			{
				return;
			}

			radians		= ( this.invertY ) ? -radians : radians;
			this.pitch -= radians;

			if( this.pitch > this.maxPitch )
			{
				radians += this.pitch - this.maxPitch;
			}
			else if( this.pitch < -this.maxPitch )
			{
				radians += this.pitch + this.maxPitch;
			}

			Matrix rotation = Matrix.RotationAxis( this.right, radians );
			this.up			= Vector3.TransformNormal( this.up, rotation );
			this.look		= Vector3.TransformNormal( this.look, rotation );
		}

		public void Roll( float radians )
		{
			if( radians == 0.0f )
			{ 
				return;
			}

			Matrix rotation = Matrix.RotationAxis( this.look, radians );
			this.right		= Vector3.TransformNormal( this.right, rotation );
			this.up			= Vector3.TransformNormal( this.up, rotation );
		}

		internal override void Update()
		{
			if( Vector3.Length( this.velocity ) > this.maxVelocity )
			{
				this.velocity = Vector3.Normalize( this.velocity ) * this.maxVelocity;
			}

			this.position  += this.velocity;
			
			this.velocity	= new Vector3();
			this.lookAt		= this.position + this.look;

			Vector3 up		= new Vector3( 0.0f, 1.0f, 0.0f );
			this.view		= Matrix.LookAtLH( Position, LookAt, up );

			this.boundingFrustrum.Rebuild( this );

			this.right.X = this.view.M11;
			this.right.Y = this.view.M21;
			this.right.Z = this.view.M31;
			this.up.X	 = this.view.M12;
			this.up.Y	 = this.view.M22;
			this.up.Z	 = this.view.M32;
			this.look.X  = this.view.M13;
			this.look.Y  = this.view.M23;
			this.look.Z  = this.view.M33;

			float lookLengthOnXZ = MathHelper.Sqrt( this.look.Z * this.look.Z + this.look.X * this.look.X );
			this.pitch  = MathHelper.Atan2( this.look.Y, lookLengthOnXZ );
			this.yaw	= MathHelper.Atan2( this.look.X, this.look.Z );
		}

		#endregion

		#region Properties

		public float MaxVelocity
		{
			get
			{
				return this.maxVelocity;
			}
			set
			{
				this.maxVelocity = value;
			}
		}

		public bool InvertY
		{
			get
			{
				return this.invertY;
			}
			set
			{
				this.invertY = value;
			}
		}

		public float CameraPitch
		{
			get
			{
				return this.pitch;
			}
		}

		public float CameraYaw
		{
			get
			{
				return this.yaw;
			}
		}

		public float MaxPitch
		{
			get
			{
				return this.maxPitch;
			}
			set
			{
				this.maxPitch = value;
			}
		}

		public bool EnableYMovement
		{
			get
			{
				return this.enableYMovement;
			}
			set
			{
				this.enableYMovement = value;
			}
		}

		#endregion
	}
}
